﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.IO;
using System.Text.RegularExpressions;
using System.Windows;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Threading;
using CrmLinkBuddyWpf.LoginWindow;
using log4net;

namespace CrmLinkBuddyWpf
{
    /// <summary>
    /// The main window (UI) for the application and associated controls.
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
            _log.Debug("Application started");
        }

        #region Class Level Members

        private static readonly ILog _log = LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);
        private CrmLogin _crmLoginControl = null;
        private string _orgUniqueName = null;
        private CrmMetadataRetriever _crmMetadataRetriever = null;
        BackgroundWorker _worker;

        #endregion Class Level Members

        /// <summary>
        /// Connect to Microsoft Dynamics CRM, and get an instance of CRMServiceClient
        /// </summary>
        private void btnSignIn_Click(object sender, RoutedEventArgs e)
        {
            // Create an instance of the XRM Tooling common login control
            _crmLoginControl = new CrmLogin();
            
            /// Wire event to the CRM sign-in response. 
            _crmLoginControl.ConnectionToCrmCompleted += ctrl_ConnectionToCrmCompleted;
            UpdateCrmConnectionStatus("Initiating connection to CRM...");
            
            /// Show the XRM Tooling common login control. 
            _crmLoginControl.ShowDialog();

            /// Validate CRM connection. If connected, enable Generate Hyperlinks button.
            if (_crmLoginControl.CrmConnectionMgr != null && _crmLoginControl.CrmConnectionMgr.CrmSvc != null && _crmLoginControl.CrmConnectionMgr.CrmSvc.IsReady)
            {
                _orgUniqueName = _crmLoginControl.CrmConnectionMgr.CrmSvc.ConnectedOrgUniqueName.ToString();
                string crmConnectionDetails = string.Format("Connected to CRM org {0} ({1})", _crmLoginControl.CrmConnectionMgr.ConnectedOrgFriendlyName, _orgUniqueName);
                _log.Debug(crmConnectionDetails);
                UpdateCrmConnectionStatus(crmConnectionDetails);
                
                btnSignIn.IsEnabled = false;
                btnGenerateHyperlinks.IsEnabled = true;
                txtText.Focus();
            }
        }

        /// <summary>
        /// To provide data to the background worker thread.
        /// </summary>
        private class LinkGenerationData
        {
            public string InputText;
        }

        /// <summary>
        /// Handles the click event for the Generate Hyperlinks button. Calls a background worker to start main process.
        /// </summary>
        private void btnGenerateHyperlinks_Click(object sender, RoutedEventArgs e)
        {
            _log.Debug("btnGenerateHyperlinks_Click begin");
            string providedText = txtText.Text;
            if (string.IsNullOrEmpty(providedText) || string.IsNullOrEmpty(providedText.Trim()))
            {
                System.Windows.MessageBox.Show("Please provide a string containing one or more CRM record GUIDs.");
                txtText.Text = null;
                txtText.Focus();
                return;
            }

            btnGenerateHyperlinks.IsEnabled = false;

            string textAsHtml = CrmLinkHtmlBuilder.ConvertTextToHtml(providedText);

            _worker = new BackgroundWorker();
            _worker.DoWork += _worker_DoWork;
            _worker.RunWorkerCompleted += _worker_RunWorkerCompleted;

            var linkGenerationData = new LinkGenerationData {
                InputText = textAsHtml
            };

            SetProgressBarMaxValue(providedText.Split('\n').Length + 1);

            _worker.RunWorkerAsync(linkGenerationData);

            _log.Debug("btnGenerateHyperlinks_Click end");
        }

        /// <summary>
        /// Background worker class. Running in a separate thread to allow for a progress bar.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void _worker_DoWork(object sender, DoWorkEventArgs e)
        {
            _log.Debug("_worker_DoWork begin");

            var data = (LinkGenerationData)e.Argument;
            string textAsHtml = data.InputText;

            UpdateProgressBar(1);

            if (_crmMetadataRetriever == null)
            {
                var crmMetadataRetriever = new CrmMetadataRetriever(_crmLoginControl.CrmConnectionMgr.CrmSvc.OrganizationServiceProxy);
                _log.Debug("RetrieveEntityMetadata");
                crmMetadataRetriever.RetrieveEntityMetadata();
                _crmMetadataRetriever = crmMetadataRetriever;
            }

            // Define Dictionary to store CRM record GUIDs that have been found. This is to avoid querying for the same GUID multiple times.
            var foundCrmRecordsCache = new Dictionary<Guid, CrmRecordFinder.RetrievedEntityRecord>();

            // Define a list for GUIDs where a record in CRM is not found
            List<Guid> guidsNotFoundInCrm = new List<Guid>();

            var htmlBuilder = new CrmLinkHtmlBuilder(_crmLoginControl.CrmConnectionMgr.CrmSvc.CrmConnectOrgUriActual.AbsoluteUri.ToString());

            var entityRankManager = new CrmEntityRankManager(_orgUniqueName, _crmMetadataRetriever);
            _log.Debug("GetEntityNamesByMostOftenFound");
            List<string> rankedEntityNames = entityRankManager.GetEntityNamesByMostOftenFound();

            var crmRecordFinder = new CrmRecordFinder(_crmLoginControl.CrmConnectionMgr.CrmSvc.OrganizationServiceProxy, _crmMetadataRetriever, rankedEntityNames);

            // Loop through the GUIDs found in the source text file. Process one text line at a time.
            Regex regex = new Regex("(?i)[A-F0-9]{8}(?:-[A-F0-9]{4}){3}-[A-Z0-9]{12}");
            int textLineOrdinal = 1;
            using (StringReader reader = new StringReader(textAsHtml))
            {
                string inputTextLine, outputTextLine;

                do
                {
                    inputTextLine = reader.ReadLine();
                    textLineOrdinal++;

                    if (!string.IsNullOrEmpty(inputTextLine))
                    {
                        outputTextLine = inputTextLine;

                        MatchCollection regexMatches = regex.Matches(inputTextLine);

                        if (regexMatches.Count == 0)
                        {
                            htmlBuilder.OutputPlainTextLine(outputTextLine);
                            UpdateProgressBar(textLineOrdinal);
                            continue;
                        }

                        foreach (Match regexMatch in regexMatches)
                        {
                            string matchedGuid = regexMatch.ToString();

                            if (matchedGuid == Guid.Empty.ToString())
                            {
                                continue;
                            }

                            Guid guid = new Guid(matchedGuid.Replace("{", "").Replace("}", ""));

                            if (guidsNotFoundInCrm.Contains(guid))
                            {
                                continue;
                            }

                            var retrievedCrmRecordInfo = new CrmRecordFinder.RetrievedEntityRecord();

                            if (foundCrmRecordsCache.ContainsKey(guid))
                            {
                                retrievedCrmRecordInfo = foundCrmRecordsCache[guid];
                            }
                            else
                            {
                                retrievedCrmRecordInfo = crmRecordFinder.RetrieveRecordById(guid);
                                if (retrievedCrmRecordInfo.EntityLogicalName != null)
                                {
                                    foundCrmRecordsCache.Add(guid, retrievedCrmRecordInfo);
                                    _log.DebugFormat("Found GUID {0}. Entity type = {1}", guid.ToString(), retrievedCrmRecordInfo.EntityLogicalName);
                                    entityRankManager.IncreaseEntityRank(retrievedCrmRecordInfo.EntityLogicalName);
                                }
                                else
                                {
                                    _log.DebugFormat("GUID not found {0}", guid.ToString());
                                    guidsNotFoundInCrm.Add(guid);
                                }
                            }

                            if (retrievedCrmRecordInfo.EntityLogicalName != null)
                            {
                                htmlBuilder.OutputLinkTextLine(inputTextLine, retrievedCrmRecordInfo.EntityLogicalName, regexMatch.ToString(),
                                    retrievedCrmRecordInfo.RecordPrimaryNameValue);
                            }
                            else
                            {
                                htmlBuilder.OutputPlainTextLine(inputTextLine);
                            }
                        }
                    }

                    UpdateProgressBar(textLineOrdinal);
                
                } while (inputTextLine != null);

                _log.Debug("WriteEntityRankToFile");
                entityRankManager.WriteEntityRankToFile();

                _log.Debug("WriteToHtmlFile");
                htmlBuilder.WriteToHtmlFile();

                _log.Debug("ShowHtmlFileInIE");
                ShowHtmlFileInIE(htmlBuilder.HtmlFilePath);
            }

            _log.Debug("_worker_DoWork end");
        }

        void _worker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            btnGenerateHyperlinks.IsEnabled = true;
        }

        /// <summary>
        /// Close the application.
        /// </summary>
        private void btnExit_Click(object sender, RoutedEventArgs e)
        {
            _log.Debug("Exit application");
            this.Close();
        }

        /// <summary>
        /// Show CRM connection status details.
        /// </summary>
        private void UpdateCrmConnectionStatus(string updateText)
        {
            lblCrmConnectionStatus.Content = updateText;
        }

        private void SetProgressBarMaxValue(int maxValue)
        {
            this.Dispatcher.Invoke(DispatcherPriority.Normal, new Action(delegate()
            {
                this.pbProgress.Maximum = maxValue;
            }));
        }

        private void UpdateProgressBar(int value)
        {
            this.Dispatcher.Invoke(DispatcherPriority.Normal, new Action(delegate()
            {
                this.pbProgress.Value = value;
            }));
        }

        /// <summary>
        /// Raised when the login process is completed.
        /// </summary>
        private void ctrl_ConnectionToCrmCompleted(object sender, EventArgs e)
        {
            if (sender is CrmLogin)
            {
                this.Dispatcher.Invoke(() =>
                {
                    ((CrmLogin)sender).Close();
                });
            }
        }

        /// <summary>
        /// Displays the rendered HTML file (with CRM hyperlinks, if any) in Internet Explorer.
        /// </summary>
        /// <param name="htmlFile"></param>
        private void ShowHtmlFileInIE(string htmlFile)
        {
            Process.Start("IExplore.exe", "file://" + htmlFile);
        }
    }
}